<?php
namespace app\common\library;

use app\common\model\Config as ConfigModel;
use think\Exception;
use EasyWeChat\Factory;



class WechatPay {
    /**
     * 单例对象
     */
    protected static $instance;

    /**
     * 单例对象
     */
    protected $easypay = [];

    protected $config = [];

    public $_error = '';

    /**
     * 静态初始化
     * 达到可以静态调用的效果
     * @access public
     * @param array $options 参数
     * @return WechatApp
     */
    public static function instance($options = [])
    {
        if (is_null(self::$instance)) {
            self::$instance = new static($options);
        }
        if(!$options){
            //静态获取数据
            self::initConfig();
        }
        return self::$instance;
    }

    /**
     * 构造函数
     */
    public function __construct(){

        $this->initConfig();
        
        $this->easypay = Factory::Payment($this->config);
    }

    /**
     * 加载参数配置
     * @return void
     */
    private function initConfig(){
        $addonConfig = get_addon_config('epay');
        $wechatapp_config = $addonConfig['wechat'];

        $this->config = [
            // 必要配置
            'serial_no'  => '232896B1230D0257122A3B74D0F8700F93177B83',
            'app_id'     => $wechatapp_config['miniapp_id'],
            'app_secret' => "bae90b086e0126db3484777e23cdb8a0",
            'mch_id'     => $wechatapp_config['mch_id'],
            'key'        => $wechatapp_config['key'],
            'key_v3'        => $wechatapp_config['key_v3'],
            // 如需使用敏感接口（如退款、发送红包等）需要配置 API 证书路径(登录商户平台下载 API 证书)
            'cert_path'  => APP_PATH . '../' . $wechatapp_config['cert_client'],   // XXX: 绝对路径！！！！
            'key_path'   => APP_PATH . '../' . $wechatapp_config['cert_key'],    // XXX: 绝对路径！！！！
            // 'notify_url' => '默认的订单回调地址',   // 你也可以在下单时单独设置来想覆盖它
            // 'sandbox'    => true,          // 设置为 false 或注释则关闭沙箱模式
        ];
    }

    /**
     * 添加分账收款方   
     * @param string $account  商户号
     * @param string $name 商户全称，必须是全称
     * @return void
     */
    public function addReceiver($account, $name){
        $params = [
            'type'          => 'MERCHANT_ID',
            'relation_type' => 'STORE_OWNER',
            'account'       => $account,
            'name'          => $name,
        ];
        $request_result = $this->easypay->profit_sharing->addReceiver($params);

        //通讯
        if(!isset($request_result['return_code']) || $request_result['return_code'] != 'SUCCESS'){
            $this->_error = $request_result['return_msg'];
            return false;
        }
        //业务
        if(!isset($request_result['result_code']) || $request_result['result_code'] != 'SUCCESS'){
            $this->_error = $request_result['err_code_des'];
            return false;
        }

        return $request_result;
    }
    

    /**
     * 删除分账收款分
     * @param string $account 商户号
     * @return void
     */
    public function deleteReceiver($account){
        $params = [
            'type'    => 'MERCHANT_ID',
            'account' => $account,
        ];

        $request_result = $this->easypay->profit_sharing->deleteReceiver($params);

        //通讯
        if(!isset($request_result['return_code']) || $request_result['return_code'] != 'SUCCESS'){
            $this->_error = $request_result['return_msg'];
            return false;
        }
        //业务
        if(!isset($request_result['result_code']) || $request_result['result_code'] != 'SUCCESS'){
            $this->_error = $request_result['err_code_des'];
            return false;
        }

        return $request_result;
    }

    /**
     * 创建单次分账订单
     * @param string $pay_no 微信支付订单号 
     * @param string $profitsharing_order_no 分账订单号
     * @param string $store_mch_id 分给的商户子商户号
     * @param int $share_amount 分账金额，单位分
     * @param string $desc 分账描述
     * @return void
     */
    public function createProfitSharing($pay_no, $profitsharing_order_no, $store_mch_id, $share_amount, $desc = ''){
        $receiver = [
            'type'        => 'MERCHANT_ID',
            'account'     => $store_mch_id,
            'amount'      => $share_amount,
            'description' => $desc,
        ];

        $request_result = $this->easypay->profit_sharing->share($pay_no, $profitsharing_order_no, $receiver);

        //通讯
        if(!isset($request_result['return_code']) || $request_result['return_code'] != 'SUCCESS'){
            $this->_error = $request_result['return_msg'];
            return false;
        }
        //业务
        if(!isset($request_result['result_code']) || $request_result['result_code'] != 'SUCCESS'){
            $this->_error = $request_result['err_code_des'];
            return false;
        }

        return $request_result;
    }

    /**
     * 分账结果查询
     * @param string $pay_no 微信支付订单号
     * @param string $profitsharing_order_no 系统内部分账订单号
     * @return void
     */
    public function checkProfitSharing($pay_no, $profitsharing_order_no){
        // 参数分别为：微信支付订单号、分账订单号
        $request_result = $this->easypay->profit_sharing->query($pay_no,  $profitsharing_order_no);

        //通讯
        if(!isset($request_result['return_code']) || $request_result['return_code'] != 'SUCCESS'){
            $this->_error = $request_result['return_msg'];
            return false;
        }

        return $request_result;
    }

    /**
     * 分账完结确认
     * @param string $pay_no    微信支付订单号
     * @param string $profitsharing_order_no    系统内部分账订单号
     * @param string $desc  分账完结描述
     * @return void
     */
    public function confirmProfitSharing($pay_no, $profitsharing_order_no, $desc = ''){

        $params = [
            'transaction_id' => $pay_no,
            'out_order_no'   => $profitsharing_order_no,
            'description'    => $desc,
        ];
        $request_result = $this->easypay->profit_sharing->markOrderAsFinished($params);
        
        //通讯
        if(!isset($request_result['return_code']) || $request_result['return_code'] != 'SUCCESS'){
            $this->_error = $request_result['return_msg'];
            return false;
        }
        //业务
        if(!isset($request_result['result_code']) || $request_result['result_code'] != 'SUCCESS'){
            $this->_error = $request_result['err_code_des'];
            return false;
        }

        return $request_result;
    }


    /**
     * 申请分账回退
     * @param string $profitsharing_order_no 分账订单号 
     * @param string $profitsharing_return_order_no 分账回退订单号
     * @param int $return_amount    回退金额
     * @param string $return_desc 回退描述
     * @return void
     */
    public function returnProfitSharing($profitsharing_order_no, $profitsharing_return_order_no, $return_amount, $return_desc){

        $request_result = $this->easypay->profit_sharing->returnShare($profitsharing_order_no,  $profitsharing_return_order_no, $return_amount, $this->config['wechat_mch_id'], $return_desc);

        //通讯
        if(!isset($request_result['return_code']) || $request_result['return_code'] != 'SUCCESS'){
            $this->_error = $request_result['return_msg'];
            return false;
        }

        return $request_result;
    }


    /**
     * 分账回退结果查询
     * @param string $profitsharing_order_no
     * @param string $profitsharing_return_order_no
     * @return void
     */
    public function returnProfitSharingQuery($profitsharing_order_no, $profitsharing_return_order_no){
        $request_result = $this->easypay->profit_sharing->returnShareQuery($profitsharing_order_no,  $profitsharing_return_order_no);

        //通讯
        if(!isset($request_result['return_code']) || $request_result['return_code'] != 'SUCCESS'){
            $this->_error = $request_result['return_msg'];
            return false;
        }

        return $request_result;
    }
    

    /**
     * 创建退款订单
     * @param string $pay_no 微信支付订单号
     * @param string $refund_order_no 退款订单号
     * @param int $total_amount  支付订单总金额
     * @param int $refund_amount 退款总金额
     * @param string $desc 退款订单描述
     * @return void
     */
    public function createRefundOrder($order_no, $refund_order_no, $total_amount, $refund_amount, $desc = ''){
        // 参数分别为：商户订单号、商户退款单号、订单金额、退款金额、其他参数
        $request_result = $this->easypay->refund->byTransactionId($order_no,  $refund_order_no,  $total_amount,  $refund_amount, [
            'refund_desc' => $desc,
            'notify_url' => domainPath('/index/index/refund_notify'),
        ]);
        //通讯
        if(!isset($request_result['return_code']) || $request_result['return_code'] != 'SUCCESS'){
            $this->_error = $request_result['return_msg'];
            return false;
        }
        //业务
        if(!isset($request_result['result_code']) || $request_result['result_code'] != 'SUCCESS'){
            $this->_error = $request_result['err_code_des'];
            return false;
        }

        return $request_result;
    }

    public function refund_notify_action(){

        $response = $this->easypay->handleRefundedNotify(function ($message, $reqInfo, $fail) {
            // 其中 $message['req_info'] 获取到的是加密信息
            // $reqInfo 为 message['req_info'] 解密后的信息
            // 你的业务逻辑...
            file_put_contents('refund_notify.log', json_encode($reqInfo)."\n\r", FILE_APPEND);

            $refund_order_no = $reqInfo['out_refund_no'];
            $refund_res = (isset($reqInfo['refund_status']) && $reqInfo['refund_status'] === 'SUCCESS')?true:false;
            $product_order_lib = (new ProductOrderLib);
            $deal_res = $product_order_lib->orderRefundNotifyDeal($refund_order_no, $refund_res);
            if(!$deal_res){
                $fail($product_order_lib->_error);
            }
            return true; // 返回 true 告诉微信“我已处理完成”
            // 或返回错误原因 $fail('参数格式校验错误');
        });
        
        $response->send();
    }

    /**
     * 企业付款到零钱
     * @param string $partner_trade_no 商户订单号
     * @param string $openid 用户openid
     * @param int $check_name  校验用户姓名选项，默认NO_CHECK
     * @param int $amount 金额
     * @param string $desc 企业付款备注
     * @return void
     */
    public function createTransferOrder($params){
        $params['check_name'] = isset($params['check_name']) ? $params['check_name'] : 'NO_CHECK';
        $params['desc'] = isset($params['desc']) ? $params['desc'] : '佣金提现到账';
        $params['amount'] = isset($params['amount']) ? $params['amount'] : 0;
        $request_result = $this->transfer_batches($params['openid'], $params['partner_trade_no'], $params['partner_trade_no'], $params['amount']);
        if(isset($request_result['batch_status']) && $request_result['batch_status'] = 'ACCEPTED'){
            $query_result = $this->queryBalanceOrder($params['partner_trade_no']);
            if (isset($query_result['detail_status'])) {
                if($query_result['detail_status'] == 'SUCCESS'){
                    return ['result_code' => 'SUCCESS'];
                } else if($query_result['detail_status'] == 'FAIL'){
                    $this->_error = $query_result['fail_reason'];
                    return ['result_code' => 'FAIL'];
                }
            }
            return ['result_code' => 'WAIT'];
        } else {
            $this->_error = $request_result['message'];
        }
        return false;
    }
    // 查询企业付款到零钱
    public function  queryBalanceOrder($partner_trade_no) {
        $request_result = $this->query_transfer_batches($partner_trade_no);
        return $request_result;
    }
         /**生成认证信息
     * @param $url
     * @param $pars
     * @param $http_method
     * @param $cert_path
     * @param $key_path
     * @param $mch_id
     * @return string
     */
    public  function getToken($url, $pars, $http_method, $cert_path, $key_path, $mch_id)
    {
        $timestamp = time();                                  // 请求时间戳
        $url_parts = parse_url($url);                         // 获取请求的绝对URL
        $nonce = $timestamp . rand('10000', '99999');     // 请求随机串
        $body = empty($pars) ? '' : json_encode((object)$pars);              // 请求报文主体
        $stream_opts = [
            "ssl" => [
                "verify_peer" => false,
                "verify_peer_name" => false,
            ]
        ];
 
        $apiclient_cert_arr = openssl_x509_parse(file_get_contents($cert_path, false, stream_context_create($stream_opts)));
        // 证书序列号
        $serial_no = $apiclient_cert_arr['serialNumberHex'];
        // 密钥
        $mch_private_key = file_get_contents($key_path, false, stream_context_create($stream_opts));
        // 商户id：文档顶部定义
        $merchant_id = $mch_id;
        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        $message = $http_method . "\n" .
            $canonical_url . "\n" .
            $timestamp . "\n" .
            $nonce . "\n" .
            $body . "\n";
        openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
        // 签名
        $sign = base64_encode($raw_sign);
        // 微信返回token
        return sprintf('WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $merchant_id, $nonce, $timestamp, $serial_no, $sign);
    }
 
 
     /**
     * @param $url
     * @param $token
     * @param null $serial_no
     * @param null $data
     * @return bool|string
     */
    public  function https_request($url, $token, $serial_no = null, $data = null)
    {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, (string)$url);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
        if (!empty($data)) {
            curl_setopt($curl, CURLOPT_POST, 1);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        }
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
 
        // 添加请求头
        $headers = [
            'Authorization:' . $token,
            'Accept: application/json',
            'Content-Type: application/json; charset=utf-8',
            'User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
        ];
 
        if (!empty($serial_no)) {
            array_push($headers, 'Wechatpay-Serial:' . $serial_no);
        }
 
        if (!empty($headers)) {
            curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        }
 
        $output = curl_exec($curl);
        curl_close($curl);
 
        return $output;
    }
 
 
    /**微信敏感信息加密
     * @param $str
     * @param $platform_path //平台证书路径
     * @return string
     * @throws Exception
     */
    private  function getEncrypt($str, $platform_path)
    {
        //$str是待加密字符串
        $public_key = file_get_contents($platform_path);
        $encrypted = '';
        if (openssl_public_encrypt($str, $encrypted, $public_key, OPENSSL_PKCS1_OAEP_PADDING)) {
            //base64编码
            $sign = base64_encode($encrypted);
        } else {
            throw new Exception('encrypt failed');
        }
        return $sign;
    }
 
    /**
     * @param $sOpenid
     * @param $sName
     * @param $sOrderId
     * @param $sDetailOrderId
     * @param $nMoney
     * @param $appid
     * @param $cert_path
     * @param $key_path
     * @param $mch_id
     * @param $serial_no
     * @param $platform_path
     * @return mixed
     * @throws Exception
     */
    public  function transfer_batches($sOpenid, $sOrderId, $sDetailOrderId, $nMoney)
    {
        
        $appid = $this->config['app_id'];
        $cert_path = $this->config['cert_path'];
        $key_path = $this->config['key_path'];
        $mch_id = $this->config['mch_id'];
        $serial_no = $this->config['serial_no'];
        $platform_path = $this->certificates($cert_path, $key_path, $mch_id, $this->config['key_v3']);
        $platform_path = $platform_path[1];
        // 付款到零钱方法url
        $url = 'https://api.mch.weixin.qq.com/v3/transfer/batches';
 
        // 转账备注 （微信用户会收到该备注）
        $tRemark = '提现到账';
        // 转账金额：微信是分为单位 *100 转换
        $transfer_amount = $nMoney * 100;
 
        // 转账接收列表设置
        $transfer_detail_list = array(
            [
                'out_detail_no' => $sDetailOrderId,                  // 明细单号
                'transfer_amount' => intval($transfer_amount),   // 转账总金额
                'transfer_remark' => $tRemark,                   // 单条转账备注
                'openid' => $sOpenid,                   // 收款方openid
                //'user_name' => $sName ? self::getEncrypt($sName, $platform_path) : '',     // 转账金额 >= 2,000元，收款用户姓名必填
            ],
        );
        // 请求参数设置
        $params = [                                                // 请求参数设置
            'appid' => $appid,                 // 文档顶部定义
            'out_batch_no' => $sOrderId,                   // 商家批次单号
            'batch_name' => '提现到账',                 // 转账的名称
            'batch_remark' => '提现到账',                 // 转账的备注
            'total_amount' => intval($transfer_amount),    // 转账总金额
            'total_num' => 1,                           // 转账总笔数
            'transfer_detail_list' => $transfer_detail_list,       // 转账接收列表
        ];
        // 获取token
        $token = self::getToken($url, $params, 'POST', $cert_path, $key_path, $mch_id);
        // 发送请求
        $res = self::https_request($url, $token, $serial_no, json_encode($params));
        // 反馈数组化
        return json_decode($res, true);
    }
     public  function query_transfer_batches($out_batch_no)
    {
         $appid = $this->config['app_id'];
         $cert_path = $this->config['cert_path'];
         $key_path = $this->config['key_path'];
         $mch_id = $this->config['mch_id'];
         $serial_no = $this->config['serial_no'];
         $platform_path = $this->certificates($cert_path, $key_path, $mch_id, $this->config['key_v3']);
         $platform_path = $platform_path[1];
         $url = 'https://api.mch.weixin.qq.com/v3/transfer/batches/out-batch-no/'.$out_batch_no.'/details/out-detail-no/'.$out_batch_no;
         // 请求参数设置
         $params = [];
         // 获取token
         $token = self::getToken($url, $params, 'GET', $cert_path, $key_path, $mch_id);
         // 发送请求
         $res = self::https_request($url, $token, $serial_no,$params);;
         // 反馈数组化
         return json_decode($res, true);
    }
    /**获取平台证书列表
     * @param $cert_path//商户证书路径(绝对路径)
     * @param $key_path //商户私钥路径(绝对路径)
     * @param $mch_id   //商户号
     * @param $key      //商户秘钥
     * @return array
     */
    public  function certificates($cert_path, $key_path, $mch_id, $key): array
    {
        $url = 'https://api.mch.weixin.qq.com/v3/certificates';
 
        $token = self::getToken($url, '', 'GET', $cert_path, $key_path, $mch_id);
 
        $res = self::https_request($url, $token);
 
        $certificate = json_decode($res, true);
 
        if (!isset($certificate['data'])) {
            $error_msg = $certificate['message'] ?? '平台证书下载失败！';
            throw new HttpException('500', $error_msg);
        }
 
        $certificate_serial_no = $certificate['data'][0]['serial_no'];//平台证书序列号
        $ciphertext = $certificate['data'][0]['encrypt_certificate']['ciphertext'];
        $associatedData = $certificate['data'][0]['encrypt_certificate']['associated_data'];
        $nonceStr = $certificate['data'][0]['encrypt_certificate']['nonce'];
        $data = self::decryptToString($ciphertext, $associatedData, $nonceStr, $key);
        if (!$data) {
            throw new HttpException(500, '获取证书解密失败');
        }
 
        $dir = ROOT_PATH . '/public/public_cert';
        !is_dir($dir) && @mkdir($dir, 0755, true);
        if (!file_exists($dir . '/wx_public_cert.pem')) {
            //保存平台证书 (https://myssl.com/cert_decode.html)获取证书序列号
            file_put_contents(ROOT_PATH . '/public/public_cert/wx_public_cert.pem', $data);
        }
        return [$certificate_serial_no, $dir . '/wx_public_cert.pem'];
    }
 
    public  function decryptToString($ciphertext, $associatedData, $nonceStr, $apiV3Key)
    {
        $str = base64_decode($ciphertext);
        if (strlen($str) <= 16) {
            return '';
        }
        // ext-sodium (default installed on >= PHP 7.2) 如果没有该函数需要安装sodium扩展
        return sodium_crypto_aead_aes256gcm_decrypt($str, $associatedData, $nonceStr, $apiV3Key);
    }

}